package com.moyu.myadmin.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.moyu.myadmin.model.vo.*;
import com.moyu.myadmin.service.ProcessService;
import com.moyu.myadmin.utils.ResultData;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.IOUtils;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.*;
import org.camunda.bpm.engine.task.Comment;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * @author jiaomingyu5778@gmail.com
 * @date 2022/2/28 14:16
 */

@Log4j2
@Service
public class ProcessServiceImpl implements ProcessService {

    private final TaskService taskService;

    private final RepositoryService repositoryService;

    private final HistoryService historyService;

    private final RuntimeService runtimeService;


    public ProcessServiceImpl(TaskService taskService, RepositoryService repositoryService, HistoryService historyService, RuntimeService runtimeService) {
        this.taskService = taskService;
        this.repositoryService = repositoryService;
        this.historyService = historyService;
        this.runtimeService = runtimeService;
    }

    @Override
    public Page<TaskVO> queryListPage() {
        // 查询代办总数
        long count = taskService.createTaskQuery().taskAssignee("admin").count();

        // 查询代办信息
        List<Task> taskList = taskService.createTaskQuery().taskAssignee("admin").orderByTaskCreateTime().desc().listPage(0, 10);
        List<TaskVO> entityList = new ArrayList<>(taskList.size());
        taskList.forEach(task -> {
            TaskVO taskEntity = new TaskVO();
            taskEntity.setTaskId(task.getId());
            taskEntity.setTaskName(task.getName());
            taskEntity.setAssignee(task.getAssignee());
            taskEntity.setCreateTime(task.getCreateTime());
            taskEntity.setExecutionId(task.getExecutionId());
            taskEntity.setProcessDefinitionId(task.getProcessDefinitionId());
            taskEntity.setTaskDefinitionKey(task.getTaskDefinitionKey());
            taskEntity.setProcessInstanceId(task.getProcessInstanceId());
            entityList.add(taskEntity);
        });
        Page<TaskVO> tPage = new Page<>();
        tPage.setTotal(count);
        tPage.setRecords(entityList);
        return tPage;
    }

    @Override
    public String getProcessModel(String processDefinitionId) {
        String processModel;
        try {
            processModel = IOUtils.toString(repositoryService.getProcessModel(processDefinitionId), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return processModel;
    }

    @Override
    public List<TaskCommentVO> getTaskComment(String processInstanceId) {

        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime().asc().list();
        List<TaskCommentVO> listVo = new ArrayList<>(list.size());
        list.forEach(historicActivityInstance -> {
            log.info("historicActivityInstance={}", historicActivityInstance);
            String taskId = historicActivityInstance.getTaskId();

            List<Comment> commentList = taskService.getTaskComments(taskId);

            TaskCommentVO taskCommentVO = new TaskCommentVO();
            taskCommentVO.setActivityName(historicActivityInstance.getActivityName());
            taskCommentVO.setAssignee(historicActivityInstance.getAssignee());
            taskCommentVO.setActivityType(matching(historicActivityInstance.getActivityType()));
            taskCommentVO.setEndTime(dateToLocalDateTime(historicActivityInstance.getEndTime()));
            taskCommentVO.setStartTime(dateToLocalDateTime(historicActivityInstance.getStartTime()));
            taskCommentVO.setCostTime(getDatePoor(historicActivityInstance.getStartTime(), historicActivityInstance.getEndTime()));
            // 添加评论
            if (commentList != null && !commentList.isEmpty()) {
                taskCommentVO.setMessage(commentList.get(0).getFullMessage());
            } else {
                taskCommentVO.setMessage("无");
            }
            listVo.add(taskCommentVO);
        });
        return listVo;
    }

    /**
     * 获取高亮节点
     */
    @Override
    public ActivityHighLineVO getHighLightedFlows(String processInstanceId) {

        HistoricProcessInstance hisProIns = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        // 已完成节点
        List<HistoricActivityInstance> finished = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        Set<String> highPoint = new HashSet<>();
        finished.forEach(t -> highPoint.add(t.getActivityId()));
        // 待办节点
        List<HistoricActivityInstance> unfinished = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).unfinished().list();
        Set<String> waitingToDo = new HashSet<>();
        unfinished.forEach(t -> waitingToDo.add(t.getActivityId()));
        // iDo 我执行过的
        Set<String> iDo = new HashSet<>(); //存放 高亮 我的办理节点
        List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery().taskAssignee("admin").finished().processInstanceId(processInstanceId).list();
        taskInstanceList.forEach(a -> iDo.add(a.getTaskDefinitionKey()));

        // 高亮线
        Set<String> highLine2 = new HashSet<>(); //保存高亮的连线
        //获取流程定义的bpmn模型
        BpmnModelInstance bpmn = repositoryService.getBpmnModelInstance(hisProIns.getProcessDefinitionId());
        //已完成任务列表 可直接使用上面写过的
        List<HistoricActivityInstance> finishedList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).finished().orderByHistoricActivityInstanceStartTime().asc().list();
        int finishedNum = finishedList.size();
        for (int i = 0; i < finishedNum; i++) {
            HistoricActivityInstance finItem = finishedList.get(i);
            //根据 任务key 获取 bpmn元素
            ModelElementInstance domElement = bpmn.getModelElementById(finItem.getActivityId());
            //转换成 flowNode流程节点 才能获取到 输出线 和输入线
            FlowNode act = (FlowNode) domElement;
            Collection<SequenceFlow> outgoing = act.getOutgoing();
            //循环当前节点的 向下分支
            outgoing.forEach(v -> {
                String tarId = v.getTarget().getId();
                //已完成
                setFinish(highLine2, finishedList, finItem, v, tarId);
                setUnFinish(unfinished, highLine2, finItem, v, tarId);
            });
        }
        return ActivityHighLineVO.builder().highPoint(highPoint).highLine(highLine2).waitingToDo(waitingToDo).iDo(iDo).build();
    }

    /**
     * 已完成
     *
     * @param highLine2    高亮线
     * @param finishedList 已完成任务列表
     * @param finItem      当前任务
     * @param v            当前任务的输出线
     * @param tarId        当前任务的输出线的目标节点
     */
    private void setFinish(Set<String> highLine2, List<HistoricActivityInstance> finishedList, HistoricActivityInstance finItem, SequenceFlow v, String tarId) {
        for (HistoricActivityInstance setFinish : finishedList) {
            //循环历史完成节点 和当前完成节点的向下分支比对
            //如果当前完成任务 的结束时间 等于 下个任务的开始时间
            String findId = setFinish.getActivityId();
            if (tarId.equals(findId)) {
                if (finItem.getEndTime().equals(setFinish.getStartTime())) {
                    highLine2.add(v.getId());
                }
            }
        }
    }

    /**
     * 待完成
     *
     * @param unfinished 待办节点
     * @param highLine2  高亮线
     * @param finItem    已完成节点
     * @param v          当前节点的向下分支
     * @param tarId      当前节点的向下分支的目标节点
     */
    private void setUnFinish(List<HistoricActivityInstance> unfinished, Set<String> highLine2, HistoricActivityInstance finItem, SequenceFlow v, String tarId) {
        //循环未完成任务列表
        for (HistoricActivityInstance setpUnFinish : unfinished) {
            //循环待节点 和当前完成节点的向下分支比对
            String fixId = setpUnFinish.getActivityId();
            if (tarId.equals(fixId)) {
                if (finItem.getEndTime().equals(setpUnFinish.getStartTime())) {
                    highLine2.add(v.getId());
                }
            }
        }
    }

    @Override
    public ProcessInfoVO getProcessInfo(String processInstanceId, String processDefinitionId) {
        String processModel = this.getProcessModel(processDefinitionId);
        ActivityHighLineVO lightedFlows = this.getHighLightedFlows(processInstanceId);
        List<TaskCommentVO> commentVOS = this.getTaskComment(processInstanceId);
        return ProcessInfoVO.builder().processModel(processModel).activityHighLineVO(lightedFlows).taskCommentList(commentVOS).build();
    }

    @Override
    public void completeTask(CompleteTaskVO completeTask) {
        taskService.createComment(completeTask.getTaskId(), completeTask.getProcessInstanceId(), completeTask.getComment());
        taskService.complete(completeTask.getTaskId(), completeTask.getVariables());
    }

    @Override
    public Page<HistoricProcessVO> queryHistoryProcess(HistoricProcessVO historicProcess) {

        long count = historyService.createHistoricProcessInstanceQuery().finished().count();

        List<HistoricProcessInstance> instances = historyService.createHistoricProcessInstanceQuery().finished().orderByProcessInstanceEndTime().desc().listPage(0, 10);

        List<HistoricProcessVO> historicProcessVOS = new ArrayList<>();
        for (HistoricProcessInstance instance : instances) {
            HistoricProcessVO historicProcessVO = new HistoricProcessVO();

            historicProcessVO.setProcessId(instance.getId());
            historicProcessVO.setStartTime(dateToLocalDateTime(instance.getStartTime()));
            historicProcessVO.setEndTime(dateToLocalDateTime(instance.getEndTime()));
            historicProcessVO.setDurationInMillis(instance.getDurationInMillis());
            historicProcessVO.setStatus(instance.getState());

            historicProcessVOS.add(historicProcessVO);
        }

        Page<HistoricProcessVO> page = new Page<>();
        page.setTotal(count);
        page.setRecords(historicProcessVOS);

        return page;
    }

    @Override
    public void backProcess(BackProcessVO backProcess) {
        //查看流程历史节点
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().processInstanceId(backProcess.getProcessInstanceId()).orderByHistoricActivityInstanceStartTime().asc().list();
        // 退回上一个节点
        runtimeService.createProcessInstanceModification(backProcess.getProcessInstanceId()).setAnnotation(backProcess.getComment()).cancelAllForActivity(list.get(list.size() - 1).getTaskDefinitionKey()).startBeforeActivity(list.get(list.size() - 2).getTaskDefinitionKey()).execute();
    }

    /**
     * 计算时间差
     *
     * @param endTime   结束时间
     * @param startTime 开始时间
     * @return 时间差
     */
    private String getDatePoor(Date endTime, Date startTime) {
        if (endTime == null || startTime == null) {
            return "";
        }
        long nd = 86400000;
        long nh = 3600000;
        long nm = 60000;
        // 获得两个时间的毫秒时间差异
        long diff = endTime.getTime() - startTime.getTime();
        // 计算差多少天
        long day = diff / nd;
        // 计算差多少小时
        long hour = diff % nd / nh;
        // 计算差多少分钟
        long min = diff % nd % nh / nm;
        // 计算差多少秒//输出结果
        return day + "天" + hour + "小时" + min + "分钟";
    }

    /**
     * 时间类型转换
     *
     * @param date 时间
     * @return 转换后的时间
     */
    private LocalDateTime dateToLocalDateTime(Date date) {
        if (Objects.isNull(date)) {
            return null;
        }
        ZoneId zoneId = ZoneId.systemDefault();
        return date.toInstant().atZone(zoneId).toLocalDateTime();
    }

    /**
     * 流程描述转换
     *
     * @param activityType 流程类型
     * @return 流程描述
     */
    private String matching(String activityType) {
        String type;
        switch (activityType) {
            case "startEvent":
                type = "开始";
                break;
            case "userTask":
                type = "用户任务";
                break;
            case "exclusiveGateway":
                type = "排他网关";
                break;
            case "parallelGateway":
                type = "并行网关";
                break;
            case "endEvent":
                type = "结束";
                break;
            default:
                type = "未知节点";
                break;
        }
        return type;
    }

}
